package com.douya.mvc.handler;

import com.alibaba.csp.sentinel.slots.block.BlockException;
import com.douya.common.enums.CommonResultEnums;
import com.douya.common.ex.ApiException;
import com.douya.common.pojo.vo.CommonResponseVO;
import com.google.common.collect.Lists;
import com.google.common.collect.Maps;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.TypeMismatchException;
import org.springframework.http.HttpStatus;
import org.springframework.validation.BindException;
import org.springframework.validation.FieldError;
import org.springframework.web.HttpMediaTypeNotSupportedException;
import org.springframework.web.HttpRequestMethodNotSupportedException;
import org.springframework.web.bind.MethodArgumentNotValidException;
import org.springframework.web.bind.MissingServletRequestParameterException;
import org.springframework.web.bind.annotation.ExceptionHandler;
import org.springframework.web.bind.annotation.ResponseStatus;
import org.springframework.web.bind.annotation.RestControllerAdvice;
import org.springframework.web.method.annotation.MethodArgumentTypeMismatchException;
import org.springframework.web.servlet.NoHandlerFoundException;

import javax.servlet.http.HttpServletRequest;
import javax.validation.ConstraintViolation;
import javax.validation.ConstraintViolationException;
import java.nio.file.AccessDeniedException;
import java.util.List;
import java.util.Map;

/**
 * 说明：全局Web层异常处理器
 *
 * @author hcy
 * @date 2022-11-04
 */
@Slf4j
@RestControllerAdvice
public class GlobalExceptionHandler {

    /**
     * 违反约束，javax扩展定义
     */
    @ExceptionHandler(value = {ConstraintViolationException.class})
    @ResponseStatus(HttpStatus.BAD_REQUEST)
    public CommonResponseVO<?> handleConstraintViolationException(ConstraintViolationException e) {
        List<Map<String, Object>> mapList = Lists.newArrayList();
        for (ConstraintViolation<?> constraintViolation : e.getConstraintViolations()) {
            Map<String, Object> map = Maps.newHashMap();
            map.put("invalidValue", constraintViolation.getInvalidValue());
            map.put("message", constraintViolation.getMessage());
            map.put("field", constraintViolation.getPropertyPath().toString());
            mapList.add(map);
        }
        log.warn("", e);
        return CommonResponseVO.error(CommonResultEnums.INVALID_PARAMS, mapList);
    }

    /**
     * 参数类型不匹配
     */
    @ExceptionHandler(value = {TypeMismatchException.class})
    @ResponseStatus(HttpStatus.BAD_REQUEST)
    public CommonResponseVO<?> handleTypeMismatchException(TypeMismatchException e) {
        List<Map<String, Object>> mapList = Lists.newArrayList();
        Map<String, Object> map = Maps.newHashMap();
        map.put("field", ((MethodArgumentTypeMismatchException) e).getName());
        map.put("requiredType", e.getRequiredType());
        map.put("message", "参数类型不匹配");
        map.put("value", e.getValue());
        mapList.add(map);
        log.warn("", e);
        return CommonResponseVO.error(CommonResultEnums.INVALID_PARAMS, mapList);
    }

    /**
     * 绑定失败，如表单对象参数违反约束
     */
    @ExceptionHandler(value = {BindException.class})
    @ResponseStatus(HttpStatus.BAD_REQUEST)
    public CommonResponseVO<?> handleBindException(BindException e) {
        List<Map<String, Object>> mapList = Lists.newArrayList();
        for (FieldError fieldError : e.getFieldErrors()) {
            mapList.add(fieldErrorsToMap(fieldError));
        }
        log.warn("", e);
        return CommonResponseVO.error(CommonResultEnums.INVALID_PARAMS, mapList);
    }

    /**
     * 参数无效，如JSON请求参数违反约束
     */
    @ExceptionHandler(value = {MethodArgumentNotValidException.class})
    @ResponseStatus(HttpStatus.BAD_REQUEST)
    public CommonResponseVO<?> handleMethodArgumentNotValidException(MethodArgumentNotValidException e) {
        List<Map<String, Object>> mapList = Lists.newArrayList();
        for (FieldError fieldError : e.getBindingResult().getFieldErrors()) {
            mapList.add(fieldErrorsToMap(fieldError));
        }
        log.warn("", e);
        return CommonResponseVO.error(CommonResultEnums.INVALID_PARAMS, mapList);
    }


    /**
     * 参数缺失
     */
    @ExceptionHandler(value = {MissingServletRequestParameterException.class})
    @ResponseStatus(HttpStatus.BAD_REQUEST)
    public CommonResponseVO<?> missingServletRequestParameterException(MissingServletRequestParameterException e) {
        return CommonResponseVO.error(CommonResultEnums.INVALID_PARAMS, e.getMessage());
    }

    /**
     * 处理404
     */
    @ExceptionHandler(value = {NoHandlerFoundException.class})
    @ResponseStatus(HttpStatus.NOT_FOUND)
    public CommonResponseVO<?> handle404Exception(Exception e) {
        log.warn(" ", e);
        return CommonResponseVO.error(CommonResultEnums.INVALID_URI);
    }

    /**
     * 请求方法不支持
     */
    @ExceptionHandler(value = {HttpRequestMethodNotSupportedException.class, HttpMediaTypeNotSupportedException.class})
    @ResponseStatus(HttpStatus.METHOD_NOT_ALLOWED)
    public CommonResponseVO<?> handleMethodNotAllowedException(Exception e) {
        log.warn(" ", e);
        return CommonResponseVO.error(CommonResultEnums.INVALID_URI, "请求方法不支持");
    }

    /**
     * 处理403异常
     */
    @ExceptionHandler(value = {AccessDeniedException.class})
    @ResponseStatus(HttpStatus.FORBIDDEN)
    public CommonResponseVO<?> handAccessDeniedException(AccessDeniedException e, HttpServletRequest request) {
        log.warn("", e);
        return CommonResponseVO.error(CommonResultEnums.FORBIDDEN);
    }

    /**
     * 处理服务端主动抛出的异常
     */
    @ExceptionHandler(value = {ApiException.class})
    @ResponseStatus(HttpStatus.BAD_REQUEST)
    public CommonResponseVO<?> handApiException(ApiException e) {
        log.warn("", e);
        return CommonResponseVO.born(e.getCode(), e.getMessage(), e.getData());
    }

    /**
     * 处理限流异常
     */
    @ExceptionHandler(value = {BlockException.class})
    @ResponseStatus(HttpStatus.FORBIDDEN)
    public CommonResponseVO<?> handApiException(BlockException e) {
        log.warn("", e);
        return CommonResponseVO.born(CommonResultEnums.INVALID_PARAMS.getCode(), "api limit", null);
    }

    /**
     * 处理服务端异常
     */
    @ExceptionHandler(value = {Throwable.class})
    @ResponseStatus(HttpStatus.INTERNAL_SERVER_ERROR)
    public CommonResponseVO<?> handServerException(Exception e) {
        log.error("", e);
        return CommonResponseVO.error(CommonResultEnums.ERROR);
    }

    private Map<String, Object> fieldErrorsToMap(FieldError fieldError) {
        Map<String, Object> map = Maps.newHashMap();
        map.put("object", fieldError.getObjectName());
        map.put("rejectValue", fieldError.getRejectedValue());
        map.put("message", fieldError.getDefaultMessage());
        map.put("filed", fieldError.getField());
        return map;
    }
}
